package com.cc.fastdfs;

import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author CHENCHEN
 * @company 北京学佳澳教育科技
 * @create 2023-03-24 10:26
 */
@Slf4j
@Service
public class FastDFSClientService {

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 下载文件到本地临时目录
     *  当前代码是在 FastDFS 单机模式下使用，FastDFS 单机不支持多线程下载
     *  能优化的只有生成文件到本地采用多线程
     * @param fullPath 文件在FastDFS上的完整路径
     * @return 本地临时文件对象
     * @throws IOException
     */
    public File downloadFileToLocalTempDir(String fullPath) throws IOException {
        long startTime = System.currentTimeMillis();
        StorePath storePath = StorePath.parseFromUrl(fullPath);
        String progressRedisKey = "download_progress_" + storePath.getGroup() + "_" + storePath.getPath();

        // 从Redis获取下载进度
        String progressStr = stringRedisTemplate.opsForValue().get(progressRedisKey);
        final long[] progress = {StringUtils.isEmpty(progressStr) ? 0 : Long.parseLong(progressStr)};

        // 获取文件后缀名
        String fileName = storePath.getFullPath();
        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);

        String filePath = "E:\\0000\\fastdfs-download\\" + "temp_" + System.currentTimeMillis() + "." + fileExtension;
        // 创建本地文件，包含文件后缀名
        File tempFile = new File(filePath);
        File parentFile = tempFile.getParentFile();
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }
        // 分段下载文件，最多重试3次
        int retryCount = 1;
        DownloadCallback<Void> downloadCallback = new DownloadCallback<Void>() {
            @Override
            public Void recv(InputStream ins) throws IOException {
                try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile, true);
                     BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream)) {
                    byte[] buffer = new byte[32768]; // 增加到 32 KB
                    int n = 0;
                    long localStartTime = System.currentTimeMillis();
                    log.info("开始写入本地[{}]", tempFile.getPath());
                    while (-1 != (n = ins.read(buffer))) {
                        outputStream.write(buffer, 0, n);
                        // 更新下载进度
                        progress[0] += n;
                        stringRedisTemplate.opsForValue().set(progressRedisKey, String.valueOf(progress[0]));
                    }
                    outputStream.flush(); // 确保所有数据都被刷新到磁盘
                    long localEndTime = System.currentTimeMillis();
                    log.info("写入本地结束，耗时 [{}] 秒", (localEndTime - localStartTime)/1000);
                }
                return null;
            }
        };
        log.info("准备从FastDFS拉取资源");
        while (retryCount < 4) {
            log.info("第 [{}] 次写入，出现问题共重试3次...", retryCount);
            try {
                long fileSize = fastFileStorageClient.queryFileInfo(storePath.getGroup(),
                        storePath.getPath()).getFileSize();
                fastFileStorageClient.downloadFile(storePath.getGroup(), storePath.getPath(),
                        progress[0], fileSize - progress[0], downloadCallback);
                break;
            } catch (Exception e) {
                retryCount++;
                if (retryCount == 4) {
                    throw e;
                }
            }
        }
        // 删除下载进度数据
        stringRedisTemplate.delete(progressRedisKey);
        long endTime = System.currentTimeMillis();
        log.info("拉取文件到本地结束，总耗时 [{}] 秒", (endTime - startTime)/1000);
        return tempFile;
    }
}